<?php

CRequire('CObject', 'CError', 'CType', 'AppPlugin', 'CURL');

if (!defined('kAppActionGETArgument'))
	define('kAppActionGETArgument', '_action');

if (!defined('kAppTemplateSearchDepthLimit'))
	define('kAppTemplateSearchDepthLimit', 3);

if (!defined('kAppUseXHTML'))
	define('kAppUseXHTML', true);

define('kAppInputMethod', '* kAppInputMethod');

define('kAppArgumentNotValidated', 'kAppArgumentNotValidated');
define('kAppNoSuchActionError', 'kAppNoSuchActionError');

class AppBase extends CObject {
	function Run() {
		CAbstract('AppBase::Run()', $this);
	}
	
	function RedirectTo(&$url) {
		if (is_object($url))
			$u = $url->__toString();
		else
			$u = (string) $url;
		
		header('HTTP/1.1 303 See Other');
		header('Location: ' . $u);
	}
	
	function UA($what = null) {
		if (is_null($what))
			return $_SERVER['HTTP_USER_AGENT'];
		else
			return strpos($_SERVER['HTTP_USER_AGENT'], $what) !== false;
	}
}

class Application extends AppBase {
	var $_Plugins = array();
	
	function Init() {
		$this->Template = &new CObject();
	}
	
	function Run() {
		$this->SetFilter('htmlspecialchars');
		
		foreach (AppPlugin::RegisteredPlugins() as $name => $class) {
			$this->$name = &new $class($this);
			$this->_Plugins[$name] = &$this->$name;
		}
		
		if (CErr($e = $this->FindAction())) {
			$this->Error($e); return;
		}
		
		if (CErr($e = $this->Arguments())) {
			$this->Error($e); return;
		} else
			CDump($e);

		if (CErr($e = $this->FirePreExecute())) {
			$this->Error($e); return;
		}
		
		if (sizeof($this->Variants()) == 0)
			$this->AddVariant('regular');

		if (CErr($e = $this->Execute())) {
			$this->Error($e); return;
		}
		
		if (CErr($e = $this->FirePreRender())) {
			$this->Error($e); return;
		}
			
		if (CErr($e = $this->Render())) {
			$this->Error($e); return;
		}

		if (CErr($e = $this->FirePostRender())) {
			$this->Error($e); return;
		}
	}
	
	var $Action;
	var $Signature;
	
	// =====================
	// = Action Management =
	// =====================
	
	function FindAction() {
		$action = CGet(kAppActionGETArgument);
		if ($action == null)
			$action = 'Index';
		
		if ($this->HasSignature($action)) {
			$this->Action = $action;
			$this->Signature = &$this->SignatureOf($action);
		} else {
			return new CError(kAppNoSuchActionError, "No such action.", null);
		}
	}
	
	var $HasRendered = false;
	
	function Execute() {
		$args = $this->Arguments();
		if (CErr($args)) return $args;
		
		ob_start();
		$e = $this->Event('_Action_' . $this->Action, array_values($args));
		if (CErr($e)) {
			ob_end_clean();
			return $e;
		} else
			$x = ob_get_clean();
		
		if ($x != '') {
			echo $x;
			$this->HasRendered = true;
		}
	}
	
	function URLTo($action, $params = null) {
		$u = &$this->URLObjectTo($action, $params);
		return $u->__toString();
	}
	
	function &URLObjectTo($action, $params = null) {
		if (is_null($params))
			$params = array();
		
		if (is_array($params))
			$params[kAppActionGETArgument] = $action;
		
		$url = &CURL::SelfURL();
		$url2 = &$url->MakeCopy();
		if (!is_array($url->Query))
			$url2->Query = $params;
		else
			$url2->Query = array_merge($url->Query, $params);
		return $url2;
	}
	
	function HTMLInputHiddenTo($action, $params = null) {
		$s = '<input type="hidden" name="';
		$s .= htmlspecialchars(kAppActionGETArgument);
		$s .= '" value="';
		$s .= htmlspecialchars($action);
		$s .= '" ';
		if (kAppUseXHTML)
			$s .= '/';
		$s .= '>';
		
		if (!is_array($params)) return $s;

		foreach ($params as $n => $v)
			$s .= '<input type="hidden" name="'.htmlspecialchars($n).'" value="'.htmlspecialchars($v).'" '.kAppUseXHTML? '/' : '' .'>';
			
		return $s;
	}
	
	function URL() {
		return CURL::SelfURLString();
	}
	
	// =============
	// = Rendering =
	// =============
	
	function TemplateVariables() {
		return array_merge($this->Arguments(), get_object_vars($this->Template));
	}
	
	/* function SetTemplateVariable($name, $value) {
		$this->_TplVars[$name] = $value;
	} */
	
	function Render() {
		if ($this->HasRendered) return;
		
		$base = $this->TemplateDirectory();
		$name = $this->TemplateName();
		
		$file = $this->_FindTemplateIn($name, $base, $this->Variants(), array(), 1);
		
		if (!is_null($file)) {
			$this->_ShowTemplate($file);
			$this->HasRendered = true;
		}
	}
	
	function _DeepFilter($value) {
		if (is_string($value))
			return $this->Filter($value);
		else if (is_array($value)) {
			$r = array();
			$len = sizeof($value);
			foreach ($value as $k => $v)
				$r[$k] = $this->_DeepFilter($v);
			return $r;
		}
	}
	
	function _ShowTemplate($__AppFileName) {
		foreach ($this->TemplateVariables() as $__AppArgumentName => $__AppArgumentValue)
			$$__AppArgumentName = $this->_DeepFilter($__AppArgumentValue);
		
		require $__AppFileName;
	}
	
	var $_TplName = null;
	
	function TemplateName() {
		CLog("TemplateName() =");
		if (is_null($this->_TplName))
			$name = $this->TemplateNameForAction($this->Action);
		else
			$name = $this->_TplName;
		CDump($name);
		return $name;
	}
	
	function TemplateNameForAction($action) {
		return strtolower($action) . '.tpl.php';
	}
	
	function SetTemplateName($name) {
		$this->_TplName = $name;
	}
	
	function UseTemplateOfAction($name) {
		$this->SetTemplateName($this->TemplateNameForAction($name));
	}
	
	function TemplateDirectory() {
		if (defined('kAppTemplatesDir'))
			return kAppTemplatesDir;
		
		return dirname($_SERVER['SCRIPT_FILENAME']); // ?
	}
	
	function _FindTemplateIn($name, $path, $variants, $cv, $i) {		
		if ($i > kAppTemplateSearchDepthLimit)
			return null;
		
		if (is_file($file = ($path . DIRECTORY_SEPARATOR . $name)))
			return $file;
		
		foreach ($variants as $v) {
			if (in_array($v, $cv))
				continue;
			
			if (!is_dir($dir = $path . DIRECTORY_SEPARATOR . $v))
				continue;
			
			if (is_file($file = $dir . DIRECTORY_SEPARATOR . $name))
				return $file;
			
			$f = $this->_FindTemplateIn($name, $dir, $variants, array_merge($cv, array($v)), $i + 1);
			
			if (!is_null($f)) return $f;
		}
		
		return null;
	}
	
	// ==========
	// = Errors =
	// ==========
	
	function Error($e) {
		if ($this->HasRendered) return;
		
		header('HTTP/1.1 500 Internal Server Error');
		header('Content-Type: text/plain');
		echo "An error occurred while loading this page.\n\n";
		echo $e->Description();
	}
	
	// =====================
	// = Action signatures =
	// =====================
	
	function HasSignature($action) {
		return isset($this->{"_Action_$action"}) || method_exists($this, $a = "_Action_$action");
	}
	
	function &SignatureOf($action) {
	 	if (isset($this->{$a ="_Action_$action"}))
			return $this->$a;
		$a = array(); return $a;
	}
	
	function IsFlag($key) { return $key == '' || $key[0] == '*'; }
	
	var $_Arguments = null;
	
	function ArgumentNotValidated($name, $reason) {
		return new CError(kAppArgumentNotValidated, "The argument named '$name' is missing, of the wrong type, or in the wrong format.", $reason);
	}
	
	function Argument($name) {
		$args = $this->Arguments();
		return @$args[$name];
	}
	
	function Arguments() {
		if (is_null($this->_Arguments)) {
			$args = array();
			
			$f = 'CRequest';
			switch (@$this->Signature[kAppInputMethod]) {
				case 'GET': $f = 'CGet'; break;
				case 'POST': $f = 'CPost'; break;
				case 'REQUEST': $f = 'CRequest'; break;				
			}
			
			foreach ($this->Signature as $name => $type) {
				if ($this->IsFlag($name)) continue;
				
				$t = &$type;
				if (is_string($t))
					$t = &CType::OfName($t);
				
				if (is_null($t))
					$args[$name] = $f($name);
				else {
					$v = $f($name);
					if (CErr($error = $t->Coerce($v)))
						return Application::ArgumentNotValidated($name, $error);
					
					$args[$name] = $v;
				}
			}
			
			$this->_Arguments = $args;
		}
		
		return $this->_Arguments;
	}
	
	var $_Filter;
	
	function SetFilter($callable) {
		$this->_Filter = $callable;
	}
	
	function Filter($value) {
		$f = $this->_Filter;
		if (is_array($value))
			return array_map($f, $value);
		else
			return call_user_func($f, $value);
	}
	
	// ============
	// = Variants =
	// ============
	
	var $_Variants = array();

	function Variants() {
		asort($this->_Variants);
		return array_keys($this->_Variants);
	}
	
	function AddVariant($name, $priority = null) {
		if (is_null($priority)) $priority = 1.0;
		$this->_Variants[$name] = $priority;
	}
	
	// ==========
	// = Events =
	// ==========
	
	function FirePreRender() {
		$this->Event('PreRender', array());
	}
	
	function FirePreExecute() {
		$this->Event('PreExecute', array());
	}

	function FirePostRender() {
		$this->Event('PreExecute', array());
	}
	
	function Event($name, $args) {
		foreach ($this->_Plugins as $label => $plugin) {
			if (method_exists($plugin, $name)) {
				if (CErr($e = call_user_func_array(array(&$plugin, $name), $args)))
					return $e;
			}
		}
		
		if (method_exists($this, $name)) {
			if (CErr($e = call_user_func_array(array(&$this, $name), $args)))
				return $e;
		}
		
		foreach ($this->Variants() as $variant) {
			if (method_exists($this, $m = "{$name}_{$variant}")) {
				if (CErr($e = call_user_func_array(array(&$this, $m), $args)))
					return $e;
			}
		}
	}
}

?>